import java.io.*;
import java.util.Vector;

public class Disk extends Thread
{
	  // Our disc component has a semaphore to implemente its dependency on
	  // a call from the processor. The semaphore is private, and we offer 
	  // a method roda that unlocks it. Does it need to be synchronized???
	  // It needs a semaphore to avoid busy waiting, but...
	  private IntController hint;
	  private Memory mem;
	  private Semaphore sem;
	  private String fileName;
	  private Vector<Integer> diskImage;
	  private int diskSize;
	  
	  //Disk Line
	  public ProcessList diskList;
	  //Disk ID
	  private int ID;
	  int[] readData;
	  
	  public final static int OPERATION_READ = 0;
	  public final static int OPERATION_WRITE = 1;
	  public final static int OPERATION_LOAD = 2;
	  public final static int ERRORCODE_SUCCESS = 0;
	  public final static int ERRORCODE_SOMETHING_WRONG = 1;
	  public final static int ERRORCODE_ADDRESS_OUT_OF_RANGE = 2;
	  public final static int ERRORCODE_MISSING_EOF = 3;
	  public final static int BUFFER_SIZE = 128;
	  public final static int END_OF_FILE = 0xFFFFFFFF;
	  private String tabs;
	  
	  
	 
	 public Disk(IntController i, Memory m, int s, String name, int id, String t)
     {
	    hint = i;
	    mem = m;
	    sem = new Semaphore(0);
	    // remember size and create disk memory
	    diskSize = s;
	    diskList =  new ProcessList("Disk List "+id);
	    fileName = name;
	    diskImage = new Vector<Integer>(s);
	    readData = new int[BUFFER_SIZE];
	    ID = id;
	    tabs = t;
     }
			
	  public int getID() {
		return ID;
	}

	// Methods that the kernel (in CPU) should call: "roda" activates the disk
	  public void roda()
	  {
	    sem.V();
	  }
	  
	  // The thread that is the disk itself
	  public void run()
	  {
		    while (true)
		    { 
		    	// wait for some request comming from the processor
			    sem.P();
				
			    //Processor requested: now I have something to do...				 
					for (int i=0; i < 20; ++i)
			        {
				        // sleep just one quantum which is one disc turn here
				        try { sleep(50); }
				        catch (InterruptedException e){}
				        System.err.println(tabs+"Disk " + this.ID +" turn");
			        }
					
				   Process p = diskList.getFront();
				      
				    // Here go the disk interface registers
					int address = 0;
					int writeData = 0;
					int readSize = 0;
					int operation = p.reg[0];
					int errorCode = ERRORCODE_SUCCESS;
					
					//System.out.println(tabs+"OP from " + p.PID + " = "+ operation);
					
			      
					// After so many turns the disk should do its task!!!
					if (address < 0 || address >= diskSize)
						errorCode = ERRORCODE_ADDRESS_OUT_OF_RANGE;
					else
					{
							
				      switch(operation)
				      {
				      	case OPERATION_READ:
				      	  DiskFile file = p.openFiles.firstElement();
				      	  address = file.getCurrentDiskLine();
				      	  
				          System.err.println(tabs+"READ");
				          readSize = 1;
				          try {
				        	  readData[0] = diskImage.get(address);
				        	  
				        	  if(readData[0] == END_OF_FILE)
				        	  {
				        		p.reg[0] = 0;
				        		System.err.println(tabs+"Operation READ Failed: END OF FILE");
				        		errorCode = ERRORCODE_SOMETHING_WRONG;
				        	  }
				          } 
				          catch (Exception e) {
				        	 System.err.println(tabs+"Error in READ");
							  p.reg[0] = 1;
							  errorCode = ERRORCODE_SOMETHING_WRONG;
				          }
				          
				          p.reg[0] = readData[0];
				          p.reg[1] = errorCode;
				          file.incCurrentDiskLine();
				          break;
				          
				        case OPERATION_WRITE:
				          DiskFile filee = p.openFiles.firstElement();
				          address = filee.getCurrentDiskLine();
				          writeData = p.reg[1];
				          int error = 0;
				          
				          System.err.println(tabs+"WRITE");
				          if(diskImage.size() >= diskSize)
				          {
				        	  System.err.println(tabs+"Impossible WRITE: Disk Full");
				        	  p.reg[0] = 1;
							  errorCode = ERRORCODE_SOMETHING_WRONG;
				          }
				          else
				          {	        	  
				        		  diskImage.add(address, writeData);
				        		  //for(int i=0; i < diskImage.size(); i++)
				        			  //System.out.println("Pos "+ i + " = "+ diskImage.get(i));
				        		  error = writeFile();
				        		  if(error < 0)
				        			  errorCode = ERRORCODE_SOMETHING_WRONG;
				          }
				          
				          p.reg[1] = errorCode;
				          break;
				          
				        case OPERATION_LOAD:
				          address = p.reg[2];
				          
				          System.err.println(tabs+"LOAD");
				          
				          int diskIndex = address;
				          int bufferIndex = 0;
				          int SoftInt = 0;
				          while (diskImage.get(diskIndex) != END_OF_FILE)
				          {
				        	if (bufferIndex >= BUFFER_SIZE || diskIndex >= diskSize)
					        {
				        		  System.err.println(tabs+" Error on LOAD: Missing EOF");
					              errorCode = ERRORCODE_MISSING_EOF;
					              break;
					        }
				        	else
					        {
				        		//System.err.print(diskImage[diskIndex]+" = ");
				        		readData[bufferIndex] = diskImage.get(diskIndex);
				        		++diskIndex;
				        		++bufferIndex;
					        }
					        
				          }
				          
						  System.out.println(" ");
				          readSize = bufferIndex;
				          
				          //DMA
				          if(errorCode != ERRORCODE_MISSING_EOF)
				          {
				        	 //Copying to memory
				        	  for(int i=0; i < readSize; i++)
				        	  {
				        		  int aux = p.BaseRegister + i;
				        		  //System.out.println(" END " +aux);
				        		  //System.out.println(" DATA " +readData[i]);
				        		  SoftInt = mem.superWrite(aux, readData[i]);
				        		  if(SoftInt < 0)
				        		  {
				        			  System.err.println(tabs +"Ilegal Acess on Writing of Memory");
				        			  errorCode = ERRORCODE_SOMETHING_WRONG;
				        			  break;
				        		  }
				        		  
				        	  }
				          }
				          
				        //Set Processor Registry, indicating the output
			        	  p.reg[1] = errorCode;
			        	  p.reg[0] = 0;
			          break;
			          }
					}
		      // Here goes the code that generates an interrupt
		      if(ID == 0)
		    	  hint.set(5);
		      else
		    	  hint.set(6);
		      
		
		      }
	    }
	// this is to read disk initial image from a hosted text file
	  private void loadFile() throws IOException
	  {
	    FileReader f = new FileReader(fileName);
	    StreamTokenizer tokemon = new StreamTokenizer(f);
	    int bytes[] = new int[4];
	    int tok = tokemon.nextToken();
	    for (int i=0; tok != StreamTokenizer.TT_EOF && (i < diskSize); ++i)
	      {
	      for (int j=0; tok != StreamTokenizer.TT_EOF && j<4; ++j)
	        {
	        if (tokemon.ttype == StreamTokenizer.TT_NUMBER )
	          bytes[j] = (int) tokemon.nval;
	        else
	        if (tokemon.ttype == StreamTokenizer.TT_WORD )
	          bytes[j] = (int) tokemon.sval.charAt(0); 
	        else
	          System.out.println("Unexpected token at disk image!"); 
	        tok = tokemon.nextToken();      
	        }
	      diskImage.add(i, ((bytes[0]&255)<<24) | ((bytes[1]&255)<<16) | ((bytes[2]&255)<<8) | (bytes[3]&255));
	      System.out.println("Parsed "+bytes[0]+" "+bytes[1]+" " +bytes[2]+" "+bytes[3]+" = "+diskImage.get(i));
	      }
	    }
	  
	  private int writeFile()
	  {      
			try
			{
				
				FileWriter fstream = new FileWriter(fileName);
				BufferedWriter buf = new BufferedWriter(fstream);
				
				for(int i=0; i < diskImage.size(); i++)
				{
					int element = diskImage.get(i);
					//System.out.println("DiskImage "+i+" = "+diskImage.get(i));
					int[] aux = new int[4];
					String word = "";
					//break the 32bit word into 4 separate bytes
					aux[0] = element>>>24;
					aux[1] = element>>>16 & 255;
					aux[2] = element>>>8 & 255;
					aux[3] = element & 255;
					
					//System.out.println(tabs+" BYTESS: "+ aux[0]+ "   " + aux[1] + "    "+ aux[2] + "   " + aux[3]);
					word = String.valueOf(aux[0])+" "+String.valueOf(aux[1])+" "+String.valueOf(aux[2]+" "+String.valueOf(aux[3]));
					buf.write(word);
					buf.newLine();
			   }
			
				buf.close();
			}catch (Exception e) { 
			    	System.err.println(tabs+"PUT: Error in write on disk file");
			    	return -1;
		  }
			 return 0;

	  }
	  
	  public boolean fileExist(int line)
	  {
		  try { 
			  loadFile(); 
		
			  if(diskImage.size() > line)
				  return true;
			  else
				  return false;
			} catch (IOException e)  {
				System.err.println(tabs +" Erro ao ler arquivo");
				return false;
			}
	  }
	 
}
